﻿using DevExpress.Data.Filtering;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
using DevExpress.Utils;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using Microsoft.OData.Client;
using System.Collections;
using System.ComponentModel;

namespace EasyXaf.ObjectSpaces.OData;

public class ODataCollection : IBindingList, ICancelAddNew, ITypedList, IDisposable
{
    private List<object> _innerList;
    private CriteriaOperator _criteria;
    private readonly List<SortProperty> _sorting;
    private readonly ITypeInfo _masterObjectTypeInfo;
    private readonly IMemberInfo _collectionPropertyInfo;
    private readonly XafPropertyDescriptorCollection _propertyDescriptorCollection;

    private ODataObjectSpace ObjectSpace { get; }

    private DataServiceContext ODataContext => ObjectSpace.ODataContext;

    private int NewIndex { get; set; } = -1;

    public object MasterObject { get; }

    public string CollectionPropertyName { get; }

    public Type ObjectType { get; }

    public CriteriaOperator Criteria
    {
        get => _criteria;
        set
        {
            if (!ReferenceEquals(_criteria, value))
            {
                _criteria = value;
                Reload();
            }
        }
    }

    protected List<object> InnerList => _innerList ??= Init();

    public IList<SortProperty> Sorting
    {
        get => _sorting.AsReadOnly();
        set
        {
            _sorting.Clear();
            if (value != null)
            {
                _sorting.AddRange(value);
            }
            Reload();
        }
    }

    public bool IsLoaded => _innerList is not null;

    public int TopReturnedObjectsCount { get; set; }

    public bool DeleteObjectOnRemove { get; set; }

    public bool AllowEdit { get; } = true;

    public bool AllowNew { get; } = true;

    public bool AllowRemove { get; } = true;

    public int Count => InnerList.Count;

    public ODataCollection(
        ODataObjectSpace objectSpace,
        Type objectType,
        CriteriaOperator criteria = null,
        IList<SortProperty> sorting = null,
        object masterObject = null,
        string collectionPropertyName = null)
    {
        Guard.ArgumentNotNull(objectSpace, nameof(objectSpace));
        Guard.ArgumentNotNull(objectType, nameof(objectType));

        MasterObject = masterObject;
        CollectionPropertyName = collectionPropertyName;

        ObjectSpace = objectSpace;
        ObjectType = objectType;

        _criteria = criteria;
        _sorting = sorting != null ? new List<SortProperty>(sorting) : new List<SortProperty>();

        _propertyDescriptorCollection = CreatePropertyDescriptorCollection(ObjectSpace.TypesInfo.FindTypeInfo(objectType));
        _masterObjectTypeInfo = MasterObject != null ? objectSpace.TypesInfo.FindTypeInfo(MasterObject.GetType()) : null;
        _collectionPropertyInfo = _masterObjectTypeInfo?.FindMember(CollectionPropertyName);

        ObjectSpace.ObjectChanged += ObjectSpace_ObjectChanged;
        ObjectSpace.ObjectDeleted += ObjectSpace_ObjectDeleted;
        ObjectSpace.ObjectReloaded += ObjectSpace_ObjectReloaded;
    }

    private void ObjectSpace_ObjectChanged(object sender, ObjectChangedEventArgs e)
    {
        if (IsLoaded)
        {
            var index = InnerList.IndexOf(e.Object);
            if (index >= 0)
            {
                OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
            }
        }
    }

    private void ObjectSpace_ObjectDeleted(object sender, ObjectsManipulatingEventArgs e)
    {
        if (IsLoaded)
        {
            foreach (var obj in e.Objects)
            {
                RemoveCore(obj);
            }
        }
    }

    private void ObjectSpace_ObjectReloaded(object sender, ObjectManipulatingEventArgs e)
    {
        if (IsLoaded && e.Object.GetType() == ObjectType)
        {
            var oldObject = InnerList.FirstOrDefault(obj => obj == e.Object);
            if (oldObject != null)
            {
                var index = InnerList.IndexOf(oldObject);
                InnerList.RemoveAt(index);
                InnerList.Insert(index, e.Object);
                OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
            }
        }
    }

    private List<object> Init()
    {
        var result = new List<object>();
        var objectsQuery = ObjectSpace.GetObjectsQuery(ObjectType, Criteria, _sorting?.ToArray());
        foreach (var obj in objectsQuery)
        {
            if (!ObjectSpace.IsObjectToDelete(obj))
            {
                ObjectSpace.AcceptObject(obj);
                result.Add(obj);
            }
        }
        return result;
    }

    public void Reload()
    {
        ClearCore();
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }

    public void CopyTo(Array array, int index)
    {
        ((IList)InnerList).CopyTo(array, index);
    }

    bool ICollection.IsSynchronized => false;

    object ICollection.SyncRoot => this;

    IEnumerator IEnumerable.GetEnumerator()
    {
        return InnerList.GetEnumerator();
    }

    public int Add(object value)
    {
        if (!InnerList.Any(obj => value == obj))
        {
            InsertCore(Count, value);
            return Count - 1;
        }
        return -1;
    }

    public void Clear()
    {
        ClearCore();
        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
    }

    private void ClearCore()
    {
        if (_innerList != null)
        {
            _innerList.Clear();
            _innerList = null;
        }
    }

    public bool Contains(object value)
    {
        return InnerList.Contains(value);
    }

    public int IndexOf(object value)
    {
        return InnerList.IndexOf(value);
    }

    public void Insert(int index, object value)
    {
        if (!InnerList.Any(obj => obj == value))
        {
            InsertCore(index, value);
        }
    }

    private void InsertCore(int index, object value)
    {
        if (MasterObject != null && !string.IsNullOrWhiteSpace(CollectionPropertyName))
        {
            var linkDescriptor = ODataContext.GetLinkDescriptor(MasterObject, CollectionPropertyName, value);
            if (linkDescriptor == null)
            {
                ODataContext.AddLink(MasterObject, CollectionPropertyName, value);
            }
        }

        if (_collectionPropertyInfo != null && _collectionPropertyInfo.AssociatedMemberInfo != null)
        {
            if (_collectionPropertyInfo.AssociatedMemberInfo.IsList)
            {
                if (_collectionPropertyInfo.AssociatedMemberInfo.GetValue(value) is IList list)
                {
                    list.Add(MasterObject);
                }
            }
            else
            {
                _collectionPropertyInfo.AssociatedMemberInfo.SetValue(value, MasterObject);
            }
        }

        InnerList.Insert(index, value);
        OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, index));
    }

    public void Remove(object value)
    {
        RemoveCore(value);
    }

    protected bool RemoveCore(object value)
    {
        var index = InnerList.IndexOf(value);
        if (index >= 0)
        {
            RemoveCore(value, index);
            OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, index));
            return true;
        }

        return false;
    }

    public void RemoveAt(int index)
    {
        if (index >= 0 && index < Count)
        {
            RemoveCore(InnerList[index], index);
        }
    }

    private void RemoveCore(object obj, int index)
    {
        if (MasterObject != null && !string.IsNullOrWhiteSpace(CollectionPropertyName))
        {
            ODataContext.DeleteLink(MasterObject, CollectionPropertyName, obj);
        }

        if (_collectionPropertyInfo != null && _collectionPropertyInfo.AssociatedMemberInfo != null)
        {
            if (_collectionPropertyInfo.AssociatedMemberInfo.IsList)
            {
                if (_collectionPropertyInfo.AssociatedMemberInfo.GetValue(obj) is IList list)
                {
                    list.Remove(MasterObject);
                }
            }
            else
            {
                _collectionPropertyInfo.AssociatedMemberInfo.SetValue(obj, null);
            }
        }

        InnerList.RemoveAt(index);

        if (DeleteObjectOnRemove)
        {
            ObjectSpace.Delete(obj);
        }
    }

    bool IList.IsFixedSize => false;

    bool IList.IsReadOnly => false;

    public object this[int index]
    {
        get => InnerList[index];
        set => InnerList[index] = value;
    }

    void IBindingList.AddIndex(PropertyDescriptor property)
    {
    }

    void IBindingList.RemoveIndex(PropertyDescriptor property)
    {
    }

    object IBindingList.AddNew()
    {
        var obj = ObjectSpace.CreateObject(ObjectType);
        NewIndex = Add(obj);

        return obj;
    }

    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
        Sorting = new[]
        {
            new SortProperty(property.Name, direction == ListSortDirection.Ascending ? SortingDirection.Ascending : SortingDirection.Descending)
        };
    }

    int IBindingList.Find(PropertyDescriptor property, object key)
    {
        for (var i = 0; i < Count; i++)
        {
            var propertyValue = property.GetValue(InnerList[i]);
            if (Equals(propertyValue, key))
            {
                return i;
            }
        }
        return -1;
    }

    void IBindingList.RemoveSort()
    {
        Sorting = null;
    }

    bool IBindingList.IsSorted => Sorting.Count > 0;

    ListSortDirection IBindingList.SortDirection => Sorting.Count > 0 && Sorting[0].Direction == SortingDirection.Ascending ? ListSortDirection.Ascending : ListSortDirection.Descending;

    PropertyDescriptor IBindingList.SortProperty => Sorting.Count > 0 ? _propertyDescriptorCollection.Find(Sorting[0].PropertyName, false) : null;

    bool IBindingList.SupportsChangeNotification => true;

    bool IBindingList.SupportsSearching => true;

    bool IBindingList.SupportsSorting => true;

    public event ListChangedEventHandler ListChanged;

    protected void OnListChanged(ListChangedEventArgs e)
    {
        ListChanged?.Invoke(this, e);
    }

    public void Dispose()
    {
        ClearCore();
        ListChanged = null;

        ObjectSpace.ObjectChanged -= ObjectSpace_ObjectChanged;
        ObjectSpace.ObjectDeleted -= ObjectSpace_ObjectDeleted;
        ObjectSpace.ObjectReloaded -= ObjectSpace_ObjectReloaded;

        GC.SuppressFinalize(this);
    }

    void ICancelAddNew.CancelNew(int itemIndex)
    {
        if (NewIndex > -1 && NewIndex == itemIndex)
        {
            var obj = InnerList[NewIndex];
            RemoveCore(obj);
            ObjectSpace.RemoveFromModifiedObjects(obj);
            NewIndex = -1;
        }
    }

    void ICancelAddNew.EndNew(int itemIndex)
    {
        if (NewIndex > -1 && itemIndex == NewIndex)
        {
            NewIndex = -1;
        }
    }

    PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
    {
        return _propertyDescriptorCollection;
    }

    string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
    {
        return string.Empty;
    }

    private static XafPropertyDescriptorCollection CreatePropertyDescriptorCollection(ITypeInfo typeInfo)
    {
        var propertyDescriptorCollection = new XafPropertyDescriptorCollection(typeInfo);
        foreach (var memberInfo in typeInfo.Members)
        {
            if (memberInfo.IsVisible || memberInfo == memberInfo.Owner.KeyMember)
            {
                propertyDescriptorCollection.CreatePropertyDescriptor(memberInfo, memberInfo.Name);
            }
        }
        return propertyDescriptorCollection;
    }
}

public class ODataCollection<T> : ODataCollection, IList<T>
{
    public ODataCollection(
        ODataObjectSpace objectSpace,
        CriteriaOperator criteria = null,
        IList<SortProperty> sorting = null,
        object masterObject = null,
        string collectionPropertyName = null)
        : base(objectSpace, typeof(T), criteria, sorting, masterObject, collectionPropertyName)
    {
    }

    public new T this[int index]
    {
        get => (T)base[index];
        set => base[index] = value;
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return InnerList.Cast<T>().GetEnumerator();
    }

    public void Add(T item)
    {
        base.Add(item);
    }

    public bool Contains(T value)
    {
        return base.Contains(value);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        base.CopyTo(array, arrayIndex);
    }

    public int IndexOf(T value)
    {
        return base.IndexOf(value);
    }

    public void Insert(int index, T value)
    {
        base.Insert(index, value);
    }

    public bool Remove(T value)
    {
        return RemoveCore(value);
    }

    bool ICollection<T>.IsReadOnly => false;
}